/**
 * 
 */
package the_Crash;

import java.awt.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;

import repast.simphony.context.Context;
import repast.simphony.context.space.continuous.ContinuousSpaceFactory;
import repast.simphony.context.space.continuous.ContinuousSpaceFactoryFinder;
import repast.simphony.context.space.grid.GridFactory;
import repast.simphony.context.space.grid.GridFactoryFinder;
import repast.simphony.dataLoader.ContextBuilder;
import repast.simphony.engine.environment.RunEnvironment;
import repast.simphony.random.RandomHelper;
import repast.simphony.space.continuous.ContinuousSpace;
import repast.simphony.space.continuous.RandomCartesianAdder;
import repast.simphony.space.grid.GridPoint;
import repast.simphony.space.grid.StrictBorders;
import repast.simphony.space.grid.Grid;
import repast.simphony.space.grid.GridBuilderParameters;
import repast.simphony.space.grid.SimpleGridAdder;
import the_Crash.Stylists.SignalUnit;

/**
 * @author Hariharan
 *
 */
public class CityBuilder implements ContextBuilder<Object> {

	/**
	 * Constants and Programmable Global Settings
	 */
	public static final int lanesPerStreet = 5; // Defines the Lanes per street. Includes both side traffic and the turn lane. Must be odd number.
	
	public static int lanesOnSide;  // Do not change. The static constructor sets this based on the lanesPerSteet
	
	public static final int laneWidth = 1; // Grid based width of one lane
	
	public static final int turnLaneLength = 5; // The distance before the intersection when the turn lane is created. 
												// For all other distances, the turn lanes are disabled using medians. 
	
	private static ContinuousSpace<Object> space =null; // Continuous space of this world. 
	private static Grid<Object> grid = null; // Positioning grid of this world.
	
	private static Context<Object> context; // Context of this world.
		
	public static int globalCarCount=0; // The current number of cars in the world
	
	/** 
	 * Static constructor
	 */
	static {
		lanesOnSide = lanesPerStreet /2; // gets the number of lanes for each direction		
	}
	
	@Override
	public Context<Object> build(Context<Object> context) {
		
		globalCarCount=0;
		// Global Resets
		ParameterResolver.globalReset();
		Car.globalReset();
		SourceSink.globalReset();
		Accident.globalReset();
		

		RunEnvironment.getInstance().endAt(ParameterResolver.getTerminationTick());
		
		
		context.setId ("city");
		spawnGridAndSpace(context);
		spawnCity(context);
		CityBuilder.context = context;
		return context;
	}
	
	public static Context<Object> getContext(){
		return CityBuilder.context;
	}
	
	public int getGlobalCarCount() {
		return globalCarCount;
	}
	
	/**
	 * @return The current world's Grid 
	 */
	public static Grid<Object> getGrid(){
		return grid;
	}
	
	/**
	 * Creates the world grid
	 * @param context World's context to create the grid on
	 */
	public void spawnGridAndSpace (Context<Object> context) {
		ContinuousSpaceFactory spaceFactory = ContinuousSpaceFactoryFinder.createContinuousSpaceFactory(null);
		
		// Calculate the grid size
		int gridSize = ParameterResolver.getBlockSize() * (ParameterResolver.getCitySize() - 1) +  lanesPerStreet * laneWidth * ParameterResolver.getCitySize();
				
		space = spaceFactory.createContinuousSpace ("space", context , new RandomCartesianAdder < Object >() ,
										new repast.simphony.space.continuous.StrictBorders() , gridSize  , gridSize);
		
		GridFactory gridFactory = GridFactoryFinder . createGridFactory ( null );		
		grid = gridFactory.createGrid("grid", context , 
				new GridBuilderParameters < Object >( new StrictBorders() , new SimpleGridAdder < Object >() , true , gridSize , gridSize));
	}
	
	/**
	 * Creates the city. Generates the streets, lanes, sources, sinks and traffic signals
	 * @param context
	 */
	public void spawnCity(Context<Object> context) {
		
		// Generate the city streets
		for (int x = 0; x < ParameterResolver.getCitySize() ; x++) {
			int myX = x*(ParameterResolver.getBlockSize()+ (lanesPerStreet * laneWidth))+lanesOnSide;
			Street streetX = new Street(myX, null, grid, space);			
			for(StreetCell cell: streetX.GetCells()) {				
				context.add(cell);
				cell.moveToLocation();
			}
			Street streetY = new Street(null, myX, grid, space);
			for(StreetCell cell: streetY.GetCells()) {				
				context.add(cell);
				cell.moveToLocation();
			}
		}
		
		// Generate the traffic signals
		int totalSignalsLeft=ParameterResolver.getCitySize()*ParameterResolver.getCitySize();
		int smartSignalsLeft=(int) Math.round(totalSignalsLeft*ParameterResolver.getSmartSignal()/100);
		for (int x = 0; x < ParameterResolver.getCitySize() ; x++) {
			int myX = x*(ParameterResolver.getBlockSize()+ (lanesPerStreet * laneWidth))+lanesOnSide;
			for (int y = 0; y < ParameterResolver.getCitySize() ; y++) {
				int myY = y*(ParameterResolver.getBlockSize()+ (lanesPerStreet * laneWidth))+lanesOnSide;
				Signal sig = new SmartSignal(new GridPoint(myX, myY), grid, space);
				if(RandomHelper.nextIntFromTo(1, totalSignalsLeft)<=smartSignalsLeft)
				{
					sig = new SmartSignal(new GridPoint(myX, myY), grid, space);
					smartSignalsLeft--;
				}
				else
					sig = new DumbSignal(new GridPoint(myX, myY), grid, space);
				context.add(sig);
				sig.moveToLocation();
				totalSignalsLeft--;
				
				for(SignalUnit u: sig.GetLights()) {
					context.add(u);
					u.moveToLocation();
				}
			}
		}

		// Generate the sources and sinks
		SourceSink s;
		for (int x = 0; x < ParameterResolver.getCitySize()-1 ; x++) {
			for (int y = 0; y < ParameterResolver.getCitySize()-1 ; y++) {
				//West
				int myX=x*(ParameterResolver.getBlockSize()+ (lanesPerStreet * laneWidth))+(lanesPerStreet * laneWidth);
				int myY=y*(ParameterResolver.getBlockSize()+ (lanesPerStreet * laneWidth))+(lanesPerStreet * laneWidth)+Math.round(ParameterResolver.getBlockSize()/2);
				s = new SourceSink(RandomHelper.nextDoubleFromTo(0, 0.02), context, new GridPoint(myX, myY), grid, space);
				context.add(s);
				s.initialize();
				
				//East
				myX=x*(ParameterResolver.getBlockSize()+ (lanesPerStreet * laneWidth))+(lanesPerStreet * laneWidth)+ParameterResolver.getBlockSize()-1;
			    myY=y*(ParameterResolver.getBlockSize()+ (lanesPerStreet * laneWidth))+(lanesPerStreet * laneWidth)+Math.round(ParameterResolver.getBlockSize()/2);
				s = new SourceSink(RandomHelper.nextDoubleFromTo(0, 0.02), context, new GridPoint(myX, myY), grid, space);
				context.add(s);
				s.initialize();

				//South
				myX=x*(ParameterResolver.getBlockSize()+ (lanesPerStreet * laneWidth))+(lanesPerStreet * laneWidth)+Math.round(ParameterResolver.getBlockSize()/2);
			    myY=y*(ParameterResolver.getBlockSize()+ (lanesPerStreet * laneWidth))+(lanesPerStreet * laneWidth);
				s = new SourceSink(RandomHelper.nextDoubleFromTo(0, 0.02), context, new GridPoint(myX, myY), grid, space);
				context.add(s);
				s.initialize();

				//North
				myX=x*(ParameterResolver.getBlockSize()+ (lanesPerStreet * laneWidth))+(lanesPerStreet * laneWidth)+Math.round(ParameterResolver.getBlockSize()/2);
			    myY=y*(ParameterResolver.getBlockSize()+ (lanesPerStreet * laneWidth))+(lanesPerStreet * laneWidth)+ParameterResolver.getBlockSize()-1;
				s = new SourceSink(RandomHelper.nextDoubleFromTo(0, 0.02), context, new GridPoint(myX, myY), grid, space);
				context.add(s);
				s.initialize();
			}
		}
	}
	
	//**********************************************************************************
	
	public static boolean isInContext(Object o){
		return context.contains(o);
	}
	
	//*******************************************************************************
	
	public static void addToContext(BaseSimEntity object){
		context.add(object);
		object.moveToLocation();
	}
	
	//******************************************************************************************

	public static ContinuousSpace<Object> getSpace() {
		return space;
	}
	
	//**************************************************************************
	
	public static void removeFromContext(BaseSimEntity object){
		context.remove(object);
	}

	//**************************************************************************
	
		//checks if a location is valid
		public static boolean withinGrid(double x, double y){
			return (x>=0.0 && x<grid.getDimensions().getWidth() && y>=0.0 && y<grid.getDimensions().getHeight());
		}
		
	//*************************************************************************
		//Gets objects at a location but first checks if the location is valid
		public static Iterable<Object> safeGetObjectsAt(int x, int y)
		{
			if(withinGrid(x,y))
				return grid.getObjectsAt(x,y);
			else
				return new ArrayList<Object>();
		}
		
		//*************************************************************************
		
		//removes a car from the city without registering its arrival at destination
		public static void leftCity(Car car)
		{
			CityBuilder.globalCarCount--;
			context.remove(car);
		}
		
}
